

# Defines some useful constants representing terminal codes to print things
# in color.
if (NOT WIN32)
    string(ASCII 27 Esc)
    set(ColorReset "${Esc}[m")
    set(ColorBold "${Esc}[1m")
    set(Red "${Esc}[31m")
    set(Green "${Esc}[32m")
    set(Yellow "${Esc}[33m")
    set(Blue "${Esc}[34m")
    set(Magenta "${Esc}[35m")
    set(Cyan "${Esc}[36m")
    set(White "${Esc}[37m")
    set(BoldRed "${Esc}[1;31m")
    set(BoldGreen "${Esc}[1;32m")
    set(BoldYellow "${Esc}[1;33m")
    set(BoldBlue "${Esc}[1;34m")
    set(BoldMagenta "${Esc}[1;35m")
    set(BoldCyan "${Esc}[1;36m")
    set(BoldWhite "${Esc}[1;37m")
endif ()

# Define a function 'note' that prints a message in bold cyan
function(note msg)
    # BoldCyan 颜色
    # msg 消息
    # ColorReset 重置
    message("🐈 ${BoldCyan}says: ${msg}${ColorReset}")
endfunction()

# 指定默认的构建类型
if (CMAKE_BUILD_TYPE MATCHES "")
    set(CMAKE_BUILD_TYPE "Debug")
    set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/cmake/debug/conan_toolchain.cmake")
else ()
    set(CMAKE_BUILD_TYPE "Release")
    set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/cmake/release/conan_toolchain.cmake")
endif ()


# 构建类型的变量
if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
    set(build_type_lower "debug")
    set(build_type_upper "DEBUG")
    set(build_type_hump "Debug")
else ()
    set(build_type_lower "release")
    set(build_type_upper "RELEASE")
    set(build_type_hump "Release")
endif ()

#编译参数设置
function(compile_optimization)
    #c++ 17设置 set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD 20)
    #add_definitions("-std=c++17")
    if (NOT "${CMAKE_CXX_STANDARD}")
        note("C++ version not set. Defaulting to CMAKE_CXX_STANDARD=20")
        set(CMAKE_CXX_STANDARD 20)
    endif ()

    #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std:c++20")

    if (WIN32)

        add_definitions("/utf-8")
        add_definitions("/wd4828 /wd4267")
        #set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")
        set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zf /O3 /openmp /MP /std:c++20 /bigobj /await:strict")
        set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zf /O0 /openmp /MP /std:c++20 /bigobj /await:strict")
        set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zf /O3 /openmp /MP /std:c20 /bigobj /await:strict")
        set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Zf /O0 /openmp /MP /std:c20 /bigobj /await:strict")
        #[如果确认不需要PDB, 即不需要调试开源库, 完全可以在设置里将/Zi或/ZI去掉]

        # 文件utf-8
        add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
        add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")


    else ()
        # SET(CMAKE_C_COMPILER "/usr/bin/gcc-12")
        # SET(CMAKE_CXX_COMPILER "/usr/bin/g++-12")

        #-D_GLIBCXX_USE_CXX11_ABI=1 编译后的符号是 std::__cxx11::basic_string，
        #-D_GLIBCXX_USE_CXX11_ABI=0 编译后的符号是 std::basic_string
        # Wreturn-type 协程是空实现
        #-fmodules-ts 模块
        #-fcoroutines-ts 协程
        #-fconcepts-ts 概念
        #-fsanitize=address 检查内存溢出
        #-freport-bug 打印完整的编译 bug
        # -Wunused-result 忽略返回结果
        # -fPIE 直接编译可执行文件
        # -fPIC 直接编译成库
        # -stdlib=libc++ clang c++编译

        #-pass-exit-codes ：从一个阶段以最高错误代码退出。
        #--target-help ：显示特定于目标的命令行选项。
        #--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...] ：显示特定类型的命令行选项（使用 -v --help 显示子进程的命令行选项）。
        #-dumpspecs ：显示所有内置规范字符串。
        #-dumpversion ：显示编译器的版本。
        #-dumpmachine ：显示编译器的目标处理器。
        #-print-search-dirs ：显示编译器搜索路径中的目录。
        #-print-libgcc-file-name ：显示编译器配套库的名称。
        #-print-file-name=<lib> ：显示库 <lib> 的完整路径。
        #-print-prog-name=<prog> ：显示编译器组件 <prog> 的完整路径。
        #-print-multiarch ：显示目标的规范化 GNU 三元组，用作库路径中的一个组件。
        #-print-multi-directory ：显示 libgcc 版本的根目录。
        #-print-multi-lib ：显示命令行选项和多个库搜索目录之间的映射。
        #-print-multi-os-directory ：显示操作系统库的相对路径。
        #-print-sysroot ：显示目标库目录。
        #-print-sysroot-headers-suffix ：显示用于查找标题的 sysroot 后缀。
        #-Wa,<options> ：将逗号分隔的 <options> 传递给汇编器（assembler）。
        #-Wp,<options> ：将逗号分隔的 <options> 传递给预处理器（preprocessor）。
        #-Wl,<options> ：将逗号分隔的 <options> 传递给链接器（linker）。
        #-Xassembler <arg> ：将 <arg> 传递给汇编器（assembler）。
        #-Xpreprocessor <arg> ：将 <arg> 传递给预处理器（preprocessor）。
        #-Xlinker <arg> ：将 <arg> 传递给链接器（linker）。
        #-save-temps ：不用删除中间文件。
        #-save-temps=<arg> ：不用删除指定的中间文件。
        #-no-canonical-prefixes ：在构建其他 gcc 组件的相对前缀时，不要规范化路径。
        #-pipe ：使用管道而不是中间文件。
        #-time ：为每个子流程的执行计时。
        #-specs=<file> ：使用 <file> 的内容覆盖内置规范。-std=<standard> ：假设输入源为 <standard>。--sysroot=<directory> ：使用 <directory> 作为头文件和库的根目录。
        #-B <directory> ：将 <directory> 添加到编译器的搜索路径。
        #-v ：显示编译器调用的程序。-### ：与 -v 类似，但引用的选项和命令不执行。
        #-E ：仅执行预处理（不要编译、汇编或链接）。
        #-S ：只编译（不汇编或链接）。
        #-c ：编译和汇编，但不链接。
        #-o <file> ：指定输出文件。
        #-pie ：创建一个动态链接、位置无关的可执行文件。
        #-I ：指定头文件的包含路径。
        #-L ：指定链接库的包含路径。
        #-shared ：创建共享库/动态库。
        #-static ：使用静态链接。
        #--help ：显示帮助信息。
        #--version ：显示编译器版本信息
        add_definitions(-D_GLIBCXX_USE_CXX11_ABI=1)
        #note(${CMAKE_CXX_FLAGS})
        #note(${CXX_FLAGS})

        ## -stdlib=libc++ -lc++abi
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20  -fmodules-ts -fsanitize=address -fcoroutines-ts -fconcepts-ts -fpermissive -Wreturn-type -freport-bug -Wunused-result")
       #[[ set(CXX_FLAGS
                -Wno-mismatched-new-delete
                -Wno-deprecated-register
                -isystem /usr/lib/llvm-17/lib/clang/17/include
                -isystem /usr/lib/llvm-17/include/c++/v1
                -std=c++20
                -fmodules-ts
                -fsanitize=address
                -fcoroutines-ts
                -fconcepts-ts
                -fpermissive
                -Wreturn-typ
                -freport-buge
                -Wunused-result
                -D_FILE_OFFSET_BITS=64
                -Wall
                -Werror
                -D__STDC_LIMIT_MACROS
                -g
        )]]
        set(CXX_FLAGS
                -Wno-mismatched-new-delete
                -Wno-deprecated-register
                -std=c++20
                -fmodules-ts
                -fsanitize=address
                -fcoroutines-ts
                -fconcepts-ts
                -fpermissive
                -Wreturn-typ
                -freport-buge
                -Wunused-result
                -D_FILE_OFFSET_BITS=64
                -Wall
                -Werror
                -D__STDC_LIMIT_MACROS
                -g
        )
        #compile_optimization_2()
        # 允许自动内联模块中的内联函数
        #add_compile_options(-fmodule-implicit-inline)

        # 标头单元编译
        #add_compile_options(-fmodule-header)

        # 模块惰性禁用惰性加载
        #add_compile_options(-fno-module-lazy)
        #add_compile_options(-fmodules)
        #add_compile_options(-fbuiltin-module-map)
        #add_compile_options(-fimplicit-module-maps)
        #add_compile_options(-fprebuilt-module-path=${PREBUILT_MODULE_PATH})

    endif ()

endfunction()

function(compile_optimization_2)
    # set CXXFALGS
    if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
        set(CXX_FLAGS
                /std:c++20
                /Zc:__cplusplus
                /await:strict
                /EHa
        )
    else ()
        set(CXX_FLAGS
                -std=c++20
                -D_GLIBCXX_USE_CXX11_ABI=1
                -Wno-deprecated-register
                -Wno-mismatched-new-delete
                -D_FILE_OFFSET_BITS=64
                -fPIC
                -Wall
                -Werror
                -D__STDC_LIMIT_MACROS
                -g
        )
    endif ()

endfunction()


# 用于递归添加头文件
function(include_sub_directories_recursively root_dir)

    if (IS_DIRECTORY ${root_dir})   # 当前路径是一个目录吗，是的话就加入到包含目录
        #MESSAGE("CURRENT DIR PATH: " ${root_dir})
        #屏蔽一些特殊目录
        if (NOT (${root_dir} MATCHES ".vscode|.vs|DEBUG"))
            #MESSAGE("include dir: " ${root_dir})
            include_directories(${root_dir})
        endif ()
    endif ()

    file(GLOB ALL_SUB RELATIVE ${root_dir} ${root_dir}/*) # 获得当前目录下的所有文件，让如ALL_SUB列表中
    foreach (sub ${ALL_SUB})
        if (IS_DIRECTORY ${root_dir}/${sub})
            include_sub_directories_recursively(${root_dir}/${sub}) # 对子目录递归调用，包含
        endif ()
    endforeach ()
endfunction()


# 设置MT/MTd
function(setMTOrMTd get_app_name)

    set_property(TARGET
            ${get_app_name}
            PROPERTY
            MSVC_RUNTIME_LIBRARY
            "MultiThreaded$<$<CONFIG:Debug>:Debug>")

endfunction()

# 设置MT/MTd
macro(safe_set_static_flag)
    foreach (flag_var
            CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
            CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
        if (${flag_var} MATCHES "/MD")
            string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
        endif (${flag_var} MATCHES "/MD")

    endforeach (flag_var)
endmacro()

# 修复  warning LNK4099: PDB
function(conan_fix_pdb)
    set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/ignore:4099")
endfunction()

# 使用 conan 编译
function(conan_find_libary libary_name use_link)
    # 是否找到
    set(libary_is_found ${libary_name}_FOUND)

    # 转小写：TOLOWER   转大写：TOUPPER
    string(TOLOWER "${libary_name}" libary_lower_name)

    # 去除字符中的数字
    string(REGEX REPLACE "[0-9]*\\.[0-9]|[0-9]" "" libary_name_replace ${libary_lower_name})

    set(libary_include_dirs "${libary_name_replace}_INCLUDE_DIRS_${build_type_upper}")
    set(libary_directories "${libary_name_replace}_LIBRARIES_${build_type_upper}")
    set(libary_libraries "${libary_name_replace}_COMPONENTS_${build_type_upper}")


    find_package(${libary_name} REQUIRED)
    if (NOT libary_is_found)
        note(FATAL_ERROR "${libary_name}  NOT FOUND!")
    else ()
        note("${libary_name} FOUND OK ")

        # note:  ${${libary_include_dirs}} 类似反射机制，通过字符串名称获取变量
        # 头文件
        include_directories(${${libary_include_dirs}})
        note("${libary_name} include " ${${libary_include_dirs}})

        # 链接库目录
        note("${libary_name} directories " ${${libary_directories}})
        link_directories(${${libary_directories}})

        # 链接库
        if (use_link)
            note("${libary_name} use_link ")
            note("${libary_name} libraries " ${${libary_libraries}})
            link_libraries(${${libary_libraries}})
        endif ()

    endif ()

endfunction()

# linux 平台编译
function(linux_find_libary platform_name)

    #添加头文件搜索路径
    include_directories(/usr/include)

    link_directories(/usr/lib/llvm-17/lib/libc++)

    #添加库文件搜索路径
    if (platform_name STREQUAL "ubuntu")
        link_directories(/usr/lib/x86_64-linux-gnu)
    elseif (platform_name STREQUAL "centos")
        link_directories(/usr/lib)
    elseif (platform_name STREQUAL "cygwin")
        link_directories(/usr/lib)
    endif ()

    # boost link
    link_libraries(boost_log boost_system boost_thread boost_filesystem boost_atomic boost_date_time boost_chrono boost_regex
            boost_coroutine boost_context boost_program_options
            boost_unit_test_framework
            #[[asio 仅头文件]]
            glog
            gtest
            libyaml-cpp.so
            uring      #[[asio 需要]]
    )


    get_ccache()
    get_glog()
    get_boost()
    get_nlohmann_json()
    get_yaml()
    get_fmt()
    get_asio_redis()
    #[[ get_range()]]

    # 添加其他支持
    link_libraries(pthread dl)

endfunction()

#添加Qt5
function(windows_find_qt_1)
    set(DEPEND_REPOS_DIR "D:/Temp/.depends/QT-RELEASE")
    note(status "添加Qt5库" ${DEPEND_REPOS_DIR})
    IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
        set(CMAKE_PREFIX_PATH ${DEPEND_REPOS_DIR}/lib/cmake PARENT_SCOPE)
        set(QTLIB_DIR ${DEPEND_REPOS_DIR}/lib PARENT_SCOPE)
    elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
        set(CMAKE_PREFIX_PATH ${DEPEND_REPOS_DIR}/lib/cmake PARENT_SCOPE)
        set(QTLIB_DIR ${DEPEND_REPOS_DIR}/lib PARENT_SCOPE)
    endif ()

    find_package(Qt5Widgets REQUIRED)
    find_package(Qt5Gui REQUIRED)
    find_package(Qt5OpenGL REQUIRED)
    find_package(Qt5Concurrent REQUIRED)

    #OpenGL
    find_package(OpenGL REQUIRED)
    include_directories(${Qt5Widgets_INCLUDE_DIRS} ${Qt5OpenGL_INCLUDE_DIRS})


    #添加资源文件
    #set(RCC_FILES rcc.qrc)

    # 链接Qt静态库/动态库
    #set(LINK_QT_STATIC TRUE)


    link_directories(${QTLIB_DIR})
    #[[    link_libraries(Qt5::Widgets Qt5::Gui Qt5::Core Qt5::OpenGL
                Qt5::Concurrent
    #[=[            D:/Temp/.depends/QT-RELEASE/lib/qtlibpng.lib
                D:/Temp/.depends/QT-RELEASE/plugins/imageformats/qjpeg.lib
                D:/Temp/.depends/QT-RELEASE/plugins/imageformats/qjpeg.lib
                D:/Temp/.depends/QT-RELEASE/plugins/imageformats/qgif.lib
                D:/Temp/.depends/QT-RELEASE/plugins/imageformats/qico.lib
                D:/Temp/.depends/QT-RELEASE/plugins/bearer/qgenericbearer.lib]=])]]
endfunction()


# qt 编译
function(windows_find_qt_2)

    IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
        set(CMAKE_PREFIX_PATH "E:/repository/c++/qt/6.2.3/mingw_64/lib/cmake")
        set(QTLIB_DIR "E:/repository/c++/qt/6.2.3/mingw_64/lib")
    elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
        set(CMAKE_PREFIX_PATH ${DEPEND_REPOS_DIR}/lib/cmake)
        set(QTLIB_DIR ${DEPEND_REPOS_DIR}/lib)
    endif ()

    find_package(Qt6Widgets REQUIRED)
    find_package(Qt6Gui REQUIRED)
    find_package(Qt6OpenGL REQUIRED)
    #find_package(Qt6JpegPlugin REQUIRED)

    note("qt6")

    note(${Qt6Widgets_INCLUDE_DIRS})
    include_directories(${Qt6Widgets_INCLUDE_DIRS} ${Qt6OpenGL_INCLUDE_DIRS})


    #设置app代码路径
    set(PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
    set(UI_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/ui)
    set(PROJECT_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resource)
    #打开全局moc
    set(CMAKE_AUTOMOC ON)
    #打开全局uic
    set(CMAKE_AUTOUIC ON)
    #设置UI文件搜索路径
    set(CMAKE_AUTOUIC_SEARCH_PATHS ${UI_SOURCE_DIR})
    #打开全局rcc，本示例中没有使用qrc，此句可以去掉
    set(CMAKE_AUTORCC ON)

    #link_libraries(Qt6Widgets Qt6::Gui Qt6::Core Qt6::OpenGL #[[Qt6::QJpegPlugin]])
endfunction()


#
function(find_ffmpeg_cpp source_files)
    include_sub_directories_recursively("${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/ffmpeg-cpp/ffmpeg-cpp")
    file(GLOB_RECURSE get_files ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/ffmpeg-cpp/ffmpeg-cpp/*.h
            ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/ffmpeg-cpp/ffmpeg-cpp/*.hpp
            ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/ffmpeg-cpp/ffmpeg-cpp/*.cpp)

    # PARENT_SCOPE 传递给父级
    set(${source_files} ${get_files} PARENT_SCOPE)
endfunction()

function(conan_folly)
    conan_find_libary(Boost ON)
    conan_find_libary(BZip2 ON)
    conan_find_libary(double-conversion ON)
    conan_find_libary(fmt ON)
    conan_find_libary(gflags ON)
    conan_find_libary(glog ON)
    conan_find_libary(LibLZMA ON)
    conan_find_libary(libevent ON)
    conan_find_libary(libsodium ON)
    conan_find_libary(lz4 ON)
    conan_find_libary(Snappy ON)
    conan_find_libary(ZLIB ON)
    conan_find_libary(zstd ON)
    conan_find_libary(folly ON)
endfunction()

function(other)
    conan_find_libary(leveldb ON)
    conan_find_libary(ffmpeg ON)
    conan_find_libary(qt6 ON)
    find_ffmpeg_cpp(source_file)
    conan_find_libary(dbg-macro ON)
    conan_find_libary(yaml-cpp ON)

    conan_find_libary(OpenSSL ON)
    conan_find_libary(gtest ON)


    conan_find_libary(rxcpp ON)
endfunction()


#设置app代码路径
function(USE_QT)

    add_definitions(-DQT_NO_DEBUG)
    add_definitions(-DQT_USE_STATIC)

    set(PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
    set(UI_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/ui)
    set(PROJECT_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resource)
    #打开全局moc
    set(CMAKE_AUTOMOC ON)
    #打开全局uic
    set(CMAKE_AUTOUIC ON)
    #设置UI文件搜索路径
    set(CMAKE_AUTOUIC_SEARCH_PATHS ${UI_SOURCE_DIR})

    #打开全局rcc，本示例中没有使用qrc，此句可以去掉
    set(CMAKE_AUTORCC ON)

    set(LINK_QT_STATIC TRUE)

    find_package(Qt5Widgets REQUIRED)
    find_package(Qt5Gui REQUIRED)
    find_package(Qt5OpenGL REQUIRED)
    #find_package ( Qt5Multimedia REQUIRED )
endfunction()

function(help_conan)

    note("\n\n\n\n*****************************use conan*****************************\n")
    note("debug:     conan install . -s build_type=Debug --install-folder=./cmake/debug")
    note("release:   conan install . -s build_type=Debug --install-folder=./cmake/release")
    note("\n*****************************end conan*****************************\n\n\n\n")

endfunction()

function(help_vcpkg)
    note("\n\n\n\n*****************************use vcpkg*****************************\n")
    note("cmake .. -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}\n")
    note("vcpkg root path ${VCPKG_ROOT}\n")

    note("
            CMAKE_TOOLCHAIN_FILE 与 VCPKG_TARGET_TRIPLET 要在project()命令之前 \n
            -DVCPKG_TARGET_TRIPLET == vcpkg support
            x64-windows, x64-windows-static ,arm-uwp, arm-windows, arm64-uwp, arm64-windows,
            x64-linux, x64-osx, x64-uwp, x86-uwp, x86-windows, x86-windows-static\n
            example set(VCPKG_TARGET_TRIPLET \"x64-windows\") \n")

    note("vcpkg 依赖库编译完成后的目录 _VCPKG_INSTALLED_DIR: ${_VCPKG_INSTALLED_DIR} \n")

    note("
            # 搜索想要安装的包\n
            vcpkg search curl\n

            # 安装指定的包，curl包分号后面的表示架构，可用的值为之前列出的那些。\n
            vcpkg install curl:x64-windows\n

            # 列出已经安装的包\n
            vcpkg list\n

            # 已安装的包更新\n
            vcpkg upgrade\n

            # 删除已安装的包\n
            vcpkg remove curl:x64-windows\n")
    note("\n*****************************end vcpkg*****************************\n\n\n\n")
endfunction()

# 打印列表
function(for_each_printf arrayList)
    note("\n\n\n\n*****************************start print*****************************\n")
    foreach (list_name IN LISTS ${arrayList})
        note(${list_name})
    endforeach ()
    note("\n*****************************end print*****************************\n\n\n\n")
endfunction()

function(height_language)

    #把当前目录(.)下所有源代码文件和头文件加入变量SRC_LIST
    AUX_SOURCE_DIRECTORY(. SRC_LIST)


    #判断某环境变量是否存在
    set(ENV{abc} "hello")
    if (DEFINED ENV{abc})
    endif ()

    ##### 使用ccache 加速
    #对于C代码：

    SET(CMAKE_C_COMPILER "ccache")
    SET(CMAKE_C_COMPILER_ARG1 "gcc")

    #对于C++代码：

    SET(CMAKE_CXX_COMPILER "ccache")
    SET(CMAKE_CXX_COMPILER_ARG1 "g++")

    find_program(CCACHE_FOUND ccache)
    if (CCACHE_FOUND)
        set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
        set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) # Less useful to do it for linking, see edit2
    endif (CCACHE_FOUND)


endfunction()

# Targets C++20 for a given target. also adds additional compiler options
# in order to ensure greater levels of compatibility.
function(target_cpp_20 target_name)
    note(${target_name})
    target_compile_features("${target_name}" INTERFACE cxx_std_20)

    # The /EHa flag enables standard C++ stack unwinding
    # See: https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-160
    #[[ if (MSVC)
         target_compile_options(${target_name} INTERFACE "/EHa")
     endif()

     if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
         # This definition is needed b/c the <coroutines> header needs it in order
         # to work on clang
         target_compile_definitions(${target_name} INTERFACE __cpp_impl_coroutine=1)
     endif()

     # Enables GCC support for coroutines (these are standard C++ now but GCC still
     # requires a flag for them)
     if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
         target_compile_options(${target_name} INTERFACE "-fcoroutines")
     endif()]]
endfunction()

function(add_submodules libname dir)
    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${dir}")
        file(GLOB all_modules "${dir}/*")
        foreach (module_dir ${all_modules})
            get_filename_component(module ${module_dir} NAME)
            note("Linked ${module} @ ${dir}/${module}")
            add_subdirectory("${dir}/${module}")
            target_link_libraries(${libname} INTERFACE ${module})
        endforeach ()
    endif ()
endfunction()


####################################################
## Sec. 2: Dependency Management via FetchContent ##
####################################################

set(remote_dependencies "")

# If ALWAYS_FETCH is ON, then find_or_fetch will always fetch any remote
# dependencies rather than using the ones provided by the system. This is
# useful for creating a static executable.
option(
        ALWAYS_FETCH
        "Tells find_or_fetch to always fetch packages"
        OFF)


include(FetchContent)
# find_or_fetch will search for a system installation of ${package} via
# find_package. If it fails to find one, it'll use FetchContent to download and
# build it locally.
function(find_or_fetch package repo tag)
    if (NOT ALWAYS_FETCH)
        find_package(${package} QUIET)
    endif ()

    if (ALWAYS_FETCH OR NOT ${${package}_FOUND})
        note("Fetching dependency '${package}' from ${repo}")
        include(FetchContent)
        FetchContent_Declare(
                "${package}"
                GIT_REPOSITORY "${repo}"
                GIT_TAG "${tag}"
        )
        list(APPEND remote_dependencies "${package}")
        set(remote_dependencies ${remote_dependencies} PARENT_SCOPE)
    else ()
        note("Using system cmake package for dependency '${package}'")
    endif ()
endfunction()

# Add target to build iostream module
add_custom_target(std_modules ALL
        COMMAND ${CMAKE_COMMAND} -E echo "Building standard library modules"
        COMMAND g++ -fmodules-ts -std=c++20 -c -x c++-system-header iostream
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

# Function to set up modules in GCC
function(prepare_for_module TGT)
    target_compile_options(${TGT} PUBLIC -fmodules-ts)
    set_property(TARGET ${TGT} PROPERTY CXX_STANDARD 20)
    set_property(TARGET ${TGT} PROPERTY CXX_EXTENSIONS OFF)
    add_dependencies(${TGT} std_modules)
endfunction()